#include "CameraPrecompiled.hxx"
#include "CameraBase.hxx"

#define _Basic_camera_use_default_key_mapping 0
#define _Basic_camera_default_monitor_flag MONITOR_DEFAULTTONEAREST
#define _Basic_camera_keyboard_direction_delta (1.0f)

static ByteConst s_i8PointerWheel = _To_byte Eifersucht::BasicCamera::PointerButton::Wheel;
static ByteConst s_i8PointerLeftButton = _To_byte Eifersucht::BasicCamera::PointerButton::Left;
static ByteConst s_i8PointerRightButton = _To_byte Eifersucht::BasicCamera::PointerButton::Right;
static ByteConst s_i8PointerMiddleButton = _To_byte Eifersucht::BasicCamera::PointerButton::Middle;
static ByteConst s_i8WasPressedState = _To_byte Eifersucht::BasicCamera::ButtonState::WasPressed;
static ByteConst s_i8IsPressedState = _To_byte Eifersucht::BasicCamera::ButtonState::IsPressed;
static ByteConst s_i8InvalidKeys = _To_byte Eifersucht::BasicCamera::KeyboardButton::NumKeys;
static ByteConst s_i8UnknownKey = _To_byte Eifersucht::BasicCamera::KeyboardButton::Unknown;

static ByteConst s_i8WasPressedIsPressedState = _To_byte Eifersucht::BasicCamera::ButtonState::WasPressed | 
	_To_byte Eifersucht::BasicCamera::ButtonState::IsPressed;


Eifersucht::BasicCamera::BasicCamera()
{
	m_iKeysPressed = 0;
	ZeroValueArray( m_i8x8Keys, ARRAYSIZE(m_i8x8Keys) );

	m_hWindow = GetForegroundWindow();
	
	Float32x3Const f3Eye( 0.0f, 0.0f, 0.0f ); // View params
	Float32x3Const f3Trg( 0.0f, 0.0f, 1.0f ); // View params

	_Basic_camera_proj( Reign::XM_PIDIV4, 1.0f, 1.0f, 1000.0f ); // Projection params
	_Basic_camera_view( &f3Eye, &f3Trg ); // View params

	// Resolve cursor position
	GetCursorPos( &m_l2LastPointerPosition );
	m_iPointerWheelDelta = 0;
	m_iButtonMask = 0;

	// Initialize drag rect
	SetRect( &m_l4Drag, LONG_MIN, LONG_MIN, LONG_MAX, LONG_MAX );
	m_f2RotationVelocity = Float32x2(0.0f);
	m_f3DragVelocity = Float32x3(0.0f);
	m_f3Velocity = Float32x3(0.0f);

	m_fTotalDragTimeToZero = 0.25f;
	m_fRotationFactor = 0.01f;
	m_fMovementFactor = 5.00f;
	m_fDragTimer = 0.0f;

	m_fPitch = 0.0f;
	m_fYaw = 0.0f;

	m_f2PointerDelta = Float32x2(0.0f);
	m_f3MinBoundary = Float32x3(-1.0f); 
	m_f3MaxBoundary = Float32x3(1.0f); 
	m_fPointerSmoothFrames = 2.0f;
	
	m_bPointerMiddleButtonPressed = false;
	m_bPointerRightButtonPressed = false;
	m_bPointerLeftButtonPressed = false;
	m_bResetPointerAfterMovement = false;
	m_bEnableVerticalMovement = true;
	m_bAllowPointerCapturing = true;
	m_bClipToBoundary = false;
	m_bEnableMovement = true;
	m_bMovementDrag = false;
	m_bInvertPitch = false;

	m_KeyMap.insert(KeyPair( VK_DOWN, _To_byte KeyboardButton::Backward ));
	m_KeyMap.insert(KeyPair( VK_RIGHT, _To_byte KeyboardButton::Right ));
	m_KeyMap.insert(KeyPair( VK_HOME, _To_byte KeyboardButton::Reset ));
	m_KeyMap.insert(KeyPair( VK_UP, _To_byte KeyboardButton::Forward ));
	m_KeyMap.insert(KeyPair( VK_LEFT, _To_byte KeyboardButton::Left ));
	m_KeyMap.insert(KeyPair( VK_NEXT, _To_byte KeyboardButton::Down ));
	m_KeyMap.insert(KeyPair( VK_PRIOR, _To_byte KeyboardButton::Up ));

	m_KeyMap.insert(KeyPair( VK_NUMPAD2, _To_byte KeyboardButton::Backward ));
	m_KeyMap.insert(KeyPair( VK_NUMPAD8, _To_byte KeyboardButton::Forward ));
	m_KeyMap.insert(KeyPair( VK_NUMPAD6, _To_byte KeyboardButton::Right ));
	m_KeyMap.insert(KeyPair( VK_NUMPAD4, _To_byte KeyboardButton::Left ));
	m_KeyMap.insert(KeyPair( VK_NUMPAD3, _To_byte KeyboardButton::Down ));
	m_KeyMap.insert(KeyPair( VK_NUMPAD9, _To_byte KeyboardButton::Up ));

	m_KeyMap.insert(KeyPair( _To_uint 'S', _To_byte KeyboardButton::Backward ));
	m_KeyMap.insert(KeyPair( _To_uint 'W', _To_byte KeyboardButton::Forward ));
	m_KeyMap.insert(KeyPair( _To_uint 'H', _To_byte KeyboardButton::Reset ));
	m_KeyMap.insert(KeyPair( _To_uint 'D', _To_byte KeyboardButton::Right ));
	m_KeyMap.insert(KeyPair( _To_uint 'A', _To_byte KeyboardButton::Left ));
	m_KeyMap.insert(KeyPair( _To_uint 'Q', _To_byte KeyboardButton::Down ));
	m_KeyMap.insert(KeyPair( _To_uint 'E', _To_byte KeyboardButton::Up ));
}

Void Eifersucht::BasicCamera::_Basic_camera_reset()
{
	_Basic_camera_view(&m_f3DefaultEye, &m_f3DefaultLookAt);
}

Void Eifersucht::BasicCamera::_Basic_camera_view( 
	_In_ Float32x3ConstPtr pf3Eye, 
	_In_ Float32x3ConstPtr f3LookAt 
	)
{
	if ( !pf3Eye || !f3LookAt ) return;
	static Float32x3Const s_f3Up(0.0f, 1.0f, 0.0f);

	m_f3DefaultEye = m_f3Eye = (*pf3Eye);
	m_f3DefaultLookAt = m_f3LookAt = (*f3LookAt);
	m_f4x4View = Float32x4x4::CreateLookAt( m_f3Eye, m_f3LookAt, s_f3Up );

	auto const f4x4InverseView = DirectX::XMMatrixInverse( nullptr, m_f4x4View );
	auto const v4ZBasis = f4x4InverseView.r[3];
	const Float32x3 f3ZBasis = v4ZBasis;
	const Float32x2 f2ZBasis = v4ZBasis;

	const Float32 fLen = f2ZBasis.Length();
	m_fYaw = atan2f(f3ZBasis.x, f3ZBasis.y);
	m_fPitch = -atan2f(f3ZBasis.y, fLen);
}

Void Eifersucht::BasicCamera::_Basic_camera_proj( 
	_In_ Float32 fFieldOfView, 
	_In_ Float32 fAspectRatio, 
	_In_ Float32 fNearPlane, 
	_In_ Float32 fFarPlane 
	)
{
	m_fFieldOfView = fFieldOfView;
	m_fAspectRatio = fAspectRatio;
	m_fNearPlane = fNearPlane;
	m_fFarPlane = fFarPlane;

	m_f4x4Proj = Float32x4x4::CreatePerspectiveFieldOfView(
		m_fFieldOfView, m_fAspectRatio,
		m_fNearPlane, m_fFarPlane
		);
}

inline Byte Eifersucht::BasicCamera::_Basic_camera_map_key( _In_ UInt32 uKey )
{
#if _Basic_camera_use_default_key_mapping

	switch (uKey)
	{
	case VK_NUMPAD2: case 'S': case VK_DOWN: return Key::Backward;
	case VK_NUMPAD6: case 'D': case VK_RIGHT: return Key::Right;
	case VK_NUMPAD8: case 'W': case VK_UP: return Key::Forward;
	case VK_NUMPAD3: case 'Q': case VK_PRIOR: return Key::Down;
	case VK_NUMPAD4: case 'A': case VK_LEFT: return Key::Left;
	case VK_NUMPAD9: case 'E': case VK_NEXT: return Key::Up;

	case VK_CONTROL: return Key::Ctrl;
	default: return Key::Unknown;
	}

#else

	auto Key = m_KeyMap.find(uKey);
	if ( Key != m_KeyMap.end() )
	{
		return m_KeyMap[uKey];
	}
	else
	{
		return s_i8UnknownKey;
	}

#endif
}

Void Eifersucht::BasicCamera::_Basic_camera_constraine( _In_ Float32x3Ptr pf3Point )
{
    // Constrain vector to a bounding box 
	(*pf3Point) = Float32x3::Max( (*pf3Point), m_f3MinBoundary );
	(*pf3Point) = Float32x3::Min( (*pf3Point), m_f3MaxBoundary );
}

Void Eifersucht::BasicCamera::_Basic_camera_velocity( _In_ Float32 fElapsed )
{
	//PrintToDebugWindowOnly8( "BasicCamera: Update velocity" );


	m_f2RotationVelocity = m_f2PointerDelta * m_fRotationFactor;

	Float32x3 f3Accel = m_f3KeyboardDirection;

	// The camera should not move faster in any direction
	m_f3KeyboardDirection.Normalize(f3Accel);
	// Scale the acceleration vertor
	f3Accel *= m_fMovementFactor;

	if ( m_bMovementDrag )
	{
		/// Figure out if there any acceleration this frame. 
		auto const fAccelLenSq = f3Accel.Dot(f3Accel);

		if ( fAccelLenSq > 0 )
		{
			/// The movement key was pressed:
			//> Change the velocity to acceleration
			//> Reset drag timer 
			//> Reset drag velocity

			m_f3Velocity = f3Accel;
			//m_f3Velocity += f3Accel;
			m_fDragTimer = m_fTotalDragTimeToZero;
			m_f3DragVelocity = f3Accel / m_fDragTimer;
		}
		else
		{
			/// No movement key being pressed
			//> Slowly decrease the velocity.

			if ( m_fDragTimer > 0 )
			{
				//> Drag until timer is 0
				m_f3Velocity -= m_f3DragVelocity * fElapsed;
				m_fDragTimer -= m_fDragTimer;
			}
			else
			{
				//> Zero the velocity
				m_f3Velocity = Float32x3(0.0f);
			}
		}
	}
	else
	{
		/// No drag
		//> Change to velocity
		m_f3Velocity = f3Accel;
	}
}

Void Eifersucht::BasicCamera::_Basic_camera_pointer()
{
	//PrintToDebugWindowOnly8( "BasicCamera: Update pointer" );

	Point l2CurrentPointerPosition;
	Point l2CurrentPointerPositionDelta;
	GetCursorPos( &l2CurrentPointerPosition );

	l2CurrentPointerPositionDelta.x = l2CurrentPointerPosition.x - m_l2LastPointerPosition.x;
	l2CurrentPointerPositionDelta.y = l2CurrentPointerPosition.y - m_l2LastPointerPosition.y;
	m_l2LastPointerPosition = l2CurrentPointerPosition;

	if ( m_bResetPointerAfterMovement )
	{
		/// Set the cursor position to center of the desktop, so it always has a room 
		/// to move. This is very useful, especially if the cursor is hidden. And 
		/// when the cursor will hit an edge of a screen, you can`t really tell 
		/// what happend.

		Point l2Center; ZeroValue(&l2Center);
		MONITORINFO lInfo; ZeroValue(&lInfo); 
		lInfo.cbSize = sizeof( MONITORINFO );

		/// Get the current monitor 

		auto const hMonitor = MonitorFromWindow( 
			m_hWindow, _Basic_camera_default_monitor_flag 
			);
		GetMonitorInfo(
			hMonitor, 
			&lInfo
			);

		/// Get the center of the current monitor 

		l2Center.x = (lInfo.rcMonitor.left + lInfo.rcMonitor.right) / 2l;
		l2Center.y = (lInfo.rcMonitor.top + lInfo.rcMonitor.bottom) / 2l;

		SetCursorPos( l2Center.x, l2Center.y );
		m_l2LastPointerPosition = l2Center;
	}

	auto const fPercentOfNew = 1.0f / m_fPointerSmoothFrames;
	auto const fPercentOfOld = 1.0f - fPercentOfNew;

	m_f2PointerDelta.x = m_f2PointerDelta.x * fPercentOfOld + 
		l2CurrentPointerPositionDelta.x * fPercentOfNew;
	m_f2PointerDelta.y = m_f2PointerDelta.y * fPercentOfOld + 
		l2CurrentPointerPositionDelta.y * fPercentOfNew;

	m_f2RotationVelocity = m_f2PointerDelta * m_fRotationFactor;
}

Void Eifersucht::BasicCamera::_Basic_camera_input(
	_In_ Bool bKeyboard, _In_ Bool bPointer
	)
{
	m_f3KeyboardDirection = Float32x3(0.0f);

	if ( bKeyboard && m_bEnableMovement )
	{
		if ( IsKeyPressed( m_i8x8Keys[ _To_byte KeyboardButton::Forward ] ) ) m_f3KeyboardDirection.z += _Basic_camera_keyboard_direction_delta; // z+
		if ( IsKeyPressed( m_i8x8Keys[ _To_byte KeyboardButton::Backward ] ) ) m_f3KeyboardDirection.z -= _Basic_camera_keyboard_direction_delta; // z-
		if ( IsKeyPressed( m_i8x8Keys[ _To_byte KeyboardButton::Right ] ) ) m_f3KeyboardDirection.x += _Basic_camera_keyboard_direction_delta; // x+
		if ( IsKeyPressed( m_i8x8Keys[ _To_byte KeyboardButton::Left ] ) ) m_f3KeyboardDirection.x -= _Basic_camera_keyboard_direction_delta; // x-

		if ( m_bEnableVerticalMovement )
		{
			if ( IsKeyPressed( m_i8x8Keys[ _To_byte KeyboardButton::Up ] ) ) m_f3KeyboardDirection.y += _Basic_camera_keyboard_direction_delta; // y+
			if ( IsKeyPressed( m_i8x8Keys[ _To_byte KeyboardButton::Down ] ) ) m_f3KeyboardDirection.y -= _Basic_camera_keyboard_direction_delta; // y-
		}
	}

	if ( bPointer )
	{
		_Basic_camera_pointer();
	}
}

Void Eifersucht::BasicCamera::_Basic_camera_on_key_pressed( UInt32 uKey )
{
	auto i8MappedKey = _Basic_camera_map_key(uKey);
	if ((i8MappedKey != s_i8UnknownKey) && 
		(!IsKeyPressed(m_i8x8Keys[i8MappedKey])))
	{
		m_i8x8Keys[i8MappedKey] = s_i8WasPressedIsPressedState;
		m_iKeysPressed++;
	}
}

Void Eifersucht::BasicCamera::_Basic_camera_on_key_released( UInt32 uKey )
{
	auto i8MappedKey = _Basic_camera_map_key(uKey);
	if ( i8MappedKey < s_i8InvalidKeys )
	{
		m_i8x8Keys[i8MappedKey] &= ~s_i8IsPressedState;
		m_iKeysPressed--;
	}
}

Void Eifersucht::BasicCamera::_Basic_camera_post_on_pointer_pressed()
{
	if ( m_bAllowPointerCapturing )
	{
		SetCapture(m_hWindow);
	}

	GetCursorPos(&m_l2LastPointerPosition);
}

Void Eifersucht::BasicCamera::_Basic_camera_post_on_pointer_released()
{
	if ((m_bAllowPointerCapturing) && 
		(!m_bPointerLeftButtonPressed) && 
		(!m_bPointerRightButtonPressed) && 
		(!m_bPointerMiddleButtonPressed))
	{
		ReleaseCapture();
	}
}

Void Eifersucht::BasicCamera::_Basic_camera_on_pointer_left_pressed( Int16 iX, Int16 iY )
{
	//PrintToDebugWindowOnly8( "Eifersucht::BasicCamera::_Basic_camera_on_pointer_left_pressed." );

	PointConst l2CursorPos = { iX, iY };

	if (PtInRect(&m_l4Drag, l2CursorPos))
	{
		m_bPointerLeftButtonPressed = true;
		m_iButtonMask |= s_i8PointerLeftButton;
	}
}

Void Eifersucht::BasicCamera::_Basic_camera_on_pointer_right_pressed( Int16 iX, Int16 iY )
{
	//PrintToDebugWindowOnly8( "Eifersucht::BasicCamera::_Basic_camera_on_pointer_right_pressed." );

	PointConst l2CursorPos = { iX, iY };

	if (PtInRect(&m_l4Drag, l2CursorPos))
	{
		m_bPointerRightButtonPressed = true;
		m_iButtonMask |= s_i8PointerRightButton;
	}
}

Void Eifersucht::BasicCamera::_Basic_camera_on_pointer_middle_pressed( Int16 iX, Int16 iY )
{
	//PrintToDebugWindowOnly8( "Eifersucht::BasicCamera::_Basic_camera_on_pointer_middle_pressed." );

	PointConst l2CursorPos = { iX, iY };

	if (PtInRect(&m_l4Drag, l2CursorPos))
	{
		m_bPointerMiddleButtonPressed = true;
		m_iButtonMask |= s_i8PointerMiddleButton;
	}
}

Void Eifersucht::BasicCamera::_Basic_camera_on_pointer_left_released()
{
	//PrintToDebugWindowOnly8( "Eifersucht::BasicCamera::_Basic_camera_on_pointer_left_released." );

	m_bPointerLeftButtonPressed = false;
	m_iButtonMask &= ~s_i8PointerLeftButton;
}

Void Eifersucht::BasicCamera::_Basic_camera_on_pointer_right_released()
{
	//PrintToDebugWindowOnly8( "Eifersucht::BasicCamera::_Basic_camera_on_pointer_right_released." );

	m_bPointerRightButtonPressed = false;
	m_iButtonMask &= ~s_i8PointerRightButton;
}

Void Eifersucht::BasicCamera::_Basic_camera_on_pointer_middle_released()
{
	//PrintToDebugWindowOnly8( "Eifersucht::BasicCamera::_Basic_camera_on_pointer_middle_released." );

	m_bPointerMiddleButtonPressed = false;
	m_iButtonMask &= ~s_i8PointerMiddleButton;
}

Void Eifersucht::BasicCamera::_Basic_camera_on_pointer_capture_changed()
{
	if ((m_iButtonMask & s_i8PointerLeftButton) && 
		(m_iButtonMask & s_i8PointerRightButton) && 
		(m_iButtonMask & s_i8PointerMiddleButton))
	{
		_Basic_camera_on_pointer_left_released();
		_Basic_camera_on_pointer_right_released();
		_Basic_camera_on_pointer_middle_released();
		_Basic_camera_post_on_pointer_released();
	}
}

Void Eifersucht::BasicCamera::_Basic_camera_on_pointer_wheel_changed( _In_ Int16 iWheelDelta )
{
	m_iPointerWheelDelta += iWheelDelta;
}

